package hit.core;


/**
 * Represents the various different units to represent size with.
 * */


/**
 * Represents a size to measure Products with. 
 * */
@SuppressWarnings("serial")
public class Size extends HITBase {
	
	public enum Unit {	
		// Weight Units
		POUNDS("Pounds", 453.6, UnitGroup.WEIGHT, 1),
		OUNCES("Ounces", 28.35, UnitGroup.WEIGHT, 2),
		GRAMS("Grams", 1, UnitGroup.WEIGHT, 3),
		KILOGRAMS("Kilograms", 1000, UnitGroup.WEIGHT, 4),
		
		// Volume units
		GALLONS("Gallons", 3.785, UnitGroup.VOLUME, 5),
		QUARTS("Quarts", 0.9464, UnitGroup.VOLUME, 6),
		PINTS("Pints", 0.4732, UnitGroup.VOLUME, 7),
		FLUID_OUNCES("Fluid Ounces", 0.02957, UnitGroup.VOLUME, 8),
		LITERS("Liters", 1, UnitGroup.VOLUME, 9),
		
		// Count units
		COUNT("Count", 1, UnitGroup.COUNT, 10);
		
		public enum UnitGroup {
			WEIGHT,
			VOLUME,
			COUNT,
		};
		
		/**
		 * String value to be returned by toString.
		 */
		private String name;
		private double convValue;
		private UnitGroup group;
		private int databaseID;
		
		/**
		 * Constructor.
		 * 
		 * @param s String value to be returned by toString.
		 * 
		 * {@pre s != null}
		 * 
		 * {@post toString() == s}
		 */
		private Unit(String name, double convValue, UnitGroup group, int databaseID) {
			this.name = name;
			this.convValue = convValue;
			this.group = group;
			this.databaseID = databaseID;
		}
		
		@Override
		public String toString() {
			return name;
		}
		
		public double GetConversionValue()
		{
			return convValue;
		}
		
		public UnitGroup GetGroup()
		{
			return group;
		}
		
		public int GetDatabaseID() {
			return databaseID;
		}
	};
	
	private double amount;
	private Unit type;
	
	/**
	 * Default constructor
	 * */
	public Size() {
		amount = 0;
		type = Unit.COUNT;
	}
	
	/**
	 * Construct a Size object with a starting amount and type. 
	 * @param amount the amount to start with
	 * @param type what Unit type the Size is
	 * */
	public Size(double amount, Unit type) throws IllegalArgumentException {
		this.amount = amount;
		this.type = type;
	}
	
	/**
	 * Validates that a Size object is valid, namely that if the Unit type is
	 * "count" that the amount is an integer.
	 * @param size the Size to check
	 * @return whether the Size is valid
	 * */
	public static boolean IsValid(Size size) {
		if (size == null)
			return false;
		
		if (size.GetAmount() < 0)
			return false;
		
		//if it's a count, the amount must be an integer (test with the 'floor' method)
		if (size.GetType() == Unit.COUNT && 
				Math.floor(size.GetAmount()) != size.GetAmount())
			return false;
		return true;
	}

	/**
	 * @return the amount component of the Size
	 * */
	public double GetAmount() {
		return amount;
	}

	/**
	 * Set the amount component of the Size
	 * @param amount the new amount for the Size
	 * @throws IllegalArgumentException thrown if restrictions in 
	 * {@link #isValid(Size) isValid(Size)} are not met
	 * */
	public void SetAmount(double amount) throws IllegalArgumentException {
		if (!Size.IsValid(new Size(amount, this.GetType())))
			throw new IllegalArgumentException("Invalid Size amount");
			
		this.amount = amount;
	}

	/**
	 * @return the Unit type component of the Size
	 * */
	public Unit GetType() {
		return type;
	}

	/**
	 * Set the Unit type component of the Size
	 * @param type the new type for the Size
	 * @throws IllegalArgumentException thrown if restrictions in 
	 * {@link #isValid(Size) isValid(Size)} are not met
	 * */
	public void SetType(Unit type) throws IllegalArgumentException{
		if (!Size.IsValid(new Size(this.GetAmount(), type)))
			throw new IllegalArgumentException("Invalid Size type");
		
		this.type = type;
	}
	
	@Override
	public String toString(){
		
		if (this.amount == 0 && this.type == Size.Unit.COUNT)
		{
			return "";
		}
		else if (this.type == Size.Unit.COUNT) {
			String result = (int)this.amount + " " + type.toString();
			return result;
		}
		else
		{
			String result = Double.toString(this.amount);
			result += " ";
			result += type.toString();
			
			return result;
		}
	}
	
	/**
	 * Converts to a specific Unit type.
	 * @param newType
	 * @return
	 */
	public Size ConvertTo(Unit newType)
	{		
		if (this.type.GetGroup() != newType.GetGroup())
		{
			return null;
		}
		else
		{
			Size result = new Size();
			result.type = newType;
			
			double convRate = this.type.GetConversionValue() / newType.GetConversionValue();
			result.amount = this.amount * convRate;
			
			return result;
		}
	}
	
	/**
	 * Creates a new Size from the given string and amount
	 * @param name the name of the unit
	 * @param amount the amount that the Size should contain
	 * @return a new Size object
	 * */
	public static Size SizeFromString(String name, double amount) {
		Unit unit = null;
		
		if (name.equals(Unit.COUNT.name))
			unit = Unit.COUNT;
		else if (name.equals(Unit.FLUID_OUNCES.name))
			unit = Unit.FLUID_OUNCES;
		else if (name.equals(Unit.GALLONS.name))
			unit = Unit.GALLONS;
		else if (name.equals(Unit.GRAMS.name))
			unit = Unit.GRAMS;
		else if (name.equals(Unit.KILOGRAMS.name))
			unit = Unit.KILOGRAMS;
		else if (name.equals(Unit.LITERS.name))
			unit = Unit.LITERS;
		else if (name.equals(Unit.OUNCES.name))
			unit = Unit.OUNCES;
		else if (name.equals(Unit.PINTS.name))
			unit = Unit.PINTS;
		else if (name.equals(Unit.POUNDS.name))
			unit = Unit.POUNDS;
		else if (name.equals(Unit.QUARTS.name))
			unit = Unit.QUARTS;
		else
			throw new IllegalArgumentException("Bad size type name");
		
		return new Size(amount, unit);
	}
}
